#! /usr/bin/env python
import urllib2
import difflib
import pycurl
import sys
import os
import configuration
import re
from urlparse import urlparse
from pyparsing import *

sys.path.append("engines/")
from engine import *

# Class used for cURL callback. Stores HTML from the website
class data_store:
	def __init__(self):
		self.contents = ''
		
	def body_callback(self, buf):
		self.contents = self.contents + buf

# Class used for parsing the data from a given URL
class Parser():
	def __init__(self, URL):
		# Verify that we have http:// in our URL
		URL = URL.replace("http://", "")
		URL = URL.replace("https://", "")
		URL = "http://" + URL
		
		self.URL 		 = URL
		self.script		 = ''
		self.html		 = ''
		self.server_side = False
	
	# Grabs data at the given URL
	def download_url(self, URL, user_agent, cont_on_exception = False):
		data = data_store()
		c    = pycurl.Curl()
		c.setopt(c.URL, str(URL))
		c.setopt(pycurl.USERAGENT, user_agent)
		c.setopt(pycurl.FOLLOWLOCATION, 1)
		c.setopt(c.WRITEFUNCTION, data.body_callback)
		
		try:
			c.perform()
			code = c.getinfo(pycurl.HTTP_CODE)
			c.close()
		
			if code == 404:
				print "[*] " + URL + " returned a 404 error"
				if not cont_on_exception:
					quit()
				else:
					return ""
									
			return data.contents
			
		except:
			print "[*] Error resolving host: " + URL
			
			# If we get an exception, and we are allowing continue
			# then just skip this file
			if not cont_on_exception:
				quit()
			else:
				pass
	
	# Parses any scripts from a string of data
	def extract_script(self, HTML):	
		# Grab all of the scripts that are held in separate files
		return_script = ''
		
		js_files = self.extract_script_files(HTML)
		
		for script_file in js_files:
			script = self.download_url(script_file, configuration.CURRENT_USER_AGENT['agent'], True) # Allow continue on bad host: True
			print "\t--> Downloaded script file: " + script_file
			return_script = return_script + script
		
		# Setup PyParsing for <script>
		scriptStart,scriptEnd = makeHTMLTags("script")
		scripts = scriptStart + SkipTo(scriptEnd).setResultsName("body") + scriptEnd
		
		return_script = ''
		
		# Extract all Scripts
		for tokens,start,end in scripts.scanString(HTML):
			return_script += tokens.body + "\n\n"
		
		return return_script
	
	# Parse any scripts that are remotely hosted (for example in a remote js file)
	def extract_script_files(self, HTML):
		js_files = []
		
		m = re.findall("src=\"(.*)\.js\"", HTML)
		
		url_parts = urlparse(self.URL)
		netloc	  = url_parts.netloc + "/"
		
		for link in m:
			# If there is an http:// in the link, then we leave it alone
			# Otherwise, we prepend the netloc of self.url to the link
			# to get the relative path
			link = link + ".js"
			
			link_parts  = urlparse(link)
			link_netloc = link_parts.scheme
			
			if not link_netloc:
				link = "http://" + url_parts.netloc + "/" + link
		
			js_files.append(link)
		
		return js_files
	
	# Hook for interacting with the javascript before it is
	# passed to their respective engines. Note that modifying
	# the script here could change how the JavaScript engine
	# interprets the data
	def hook(self):
		# script = self.script
		pass
	
	# Checks to see if website is checking user agents server side
	def checking_server_side(self):
		# Retrieve the HTML from the given page
		safari_html = self.download_url(self.URL, configuration.useragents.USER_AGENTS[0]['agent'])
		opera_html  = self.download_url(self.URL, configuration.useragents.USER_AGENTS[1]['agent'])
		
		# Parse the JS from the page
		safari_js = self.extract_script(safari_html)
		opera_js  = self.extract_script(opera_html)
		
		# Check the similarities
		similarity = difflib.SequenceMatcher(None, safari_js, opera_js).ratio()
		if similarity < configuration.ACCEPTABLE_SIMILARITY:
			self.server_side = True
			return True
			
		return False
	
	# Execute the JavaScript using the proprietary script engine
	# for the given mobile device
	def execute(self):
		Engine(self.URL, self.script, self.html)
		
	# Saves the scripts to the log directory
	def save_script(self):
		
		if len(self.script) >  5:
			# Directory name		
			directory = urlparse(self.URL)
			directory = directory.netloc
			directory = directory.replace(" ", "_")
			directory = directory.replace(".", "_")
			directory = configuration.LOG_DIRECTORY + directory + "/"
			
			# Filename
			browser  = configuration.CURRENT_USER_AGENT['details']
			browser  = browser.replace(" ", "_")
			browser  = browser.replace(".", "_")
			filename = browser + ".js"
			
			if not os.access(directory, os.F_OK):
				try:
					os.makedirs(directory)
					print "\t--> Created directory for this domain"
				except OSError:
					pass
			
			file_path = directory + "/" + filename
			fd = open(file_path, 'wb')
			fd.write(self.script)
			fd.close()
			print "\t--> Script saved to " + directory + filename
		
		else:
			print "\t\t--> Server returned 200, however no useful data was returned"
			
	# Writes a detailed log of the scripts activity for reference later
	def write_log(self):
		log = "URL: " + self.URL
		log = log + "\nChecking Server Side: " + str(self.server_side)
		
		if len(self.script) > 5:
			# Directory name		
			directory = urlparse(self.URL)
			directory = directory.netloc
			directory = directory.replace(" ", "_")
			directory = directory.replace(".", "_")
			directory = configuration.LOG_DIRECTORY + directory + "/"
			
			if not os.access(directory, os.F_OK):
				try:
					os.makedirs(directory)
					print "\t--> Created directory for this domain"
				except OSError:
					pass
			
			file_path = directory + "/details.log"
			fd = open(file_path, 'wb')
			fd.write(log)
			fd.close()			
			
		
	# Analyzes the given URL
	def analyze(self):
		# We first download the HTML
		self.html = self.download_url(self.URL, configuration.CURRENT_USER_AGENT['agent'])
		
		# Next we download the JavaScript
		self.script = self.extract_script(self.html)
		
		# We save the JavaScript in the log directory
		self.save_script()
		
		# We then run it through the hook
		self.hook()
			
		# Next we pass it through our JavaScript interpreter
		# Location of terminate called after throwing an instance of 'boost::python::error_already_set'
		self.execute()
		
		# And finally we execute any shellcode on our ARM emulator
		print "\t--> Script retrieved\n"
		
		# Write log to directory
		self.write_log()
